%% Copyright 2008 Kevin A. Smith 
%% Licensed under the Apache License, Version 2.0 (the "License"); 
%% you may not use this file except in compliance with the License. 
%% You may obtain a copy of the License at 
%%
%% http://www.apache.org/licenses/LICENSE-2.0 
%%
%% Unless required by applicable law or agreed to in writing, software 
%% distributed under the License is distributed on an "AS IS" BASIS, 
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
%% See the License for the specific language governing permissions and 
%% limitations under the License.

-module(aws_util).

-export([send_request/3, send_request/4, determine_parser/2]).

-define(BASE_URL, "https://ec2.amazonaws.com/?").

parse_response(Response, ParserName) ->
	{{_, _, _},
	  _,
	  Body} = Response,
	if
		ParserName == undefined ->
			Response;
		true ->
			erlsom:sax(Body, {[], [], []}, {determine_parser(ParserName, Body), callback})
	end.
	

determine_parser(ParserName, Response) when is_atom(ParserName), is_list(Response) ->
	ErrorPos = string:str(Response, "Errors"),
	if
		ErrorPos > 0 ->
			error_response_parser;
		true ->
			ParserName
	end.

prepare(Keys, Params) ->
    P1 = format_param_list(lists:flatten([Params, generate_default_params(Keys:identifier())])),
    P2 = P1 ++ [{"Signature", generate_checksum(Keys, P1)}],
    ?BASE_URL ++ url_util:encode(P2).

send_request(Keys, Params, ParserName) when is_list(Params) ->
	send_request(Keys, Params, ParserName, undefined).

send_request(Keys, Params, ParserName, HookFun) when is_list(Params), is_function(HookFun) ->
	inets:start(),
    ssl:start(),
    Req = prepare(Keys, Params),
    case http:request(get, {Req, []}, [{ssl, []}], []) of
	{ok, Response} ->
		HookFun(Response),
	    parse_response(Response, ParserName);
	Error ->
		HookFun(Error),
	    Error
    end;

send_request(Keys, Params, ParserName, HookFun) when is_list(Params), is_atom(HookFun) ->
	inets:start(),
    ssl:start(),
    Req = prepare(Keys, Params),
    case http:request(get, {Req, []}, [{ssl, []}], []) of
	{ok, Response} ->
	    parse_response(Response, ParserName);
	Error ->
	    Error
    end.
	

generate_default_params(Identifier) ->
    [{"AWSAccessKeyId", [Identifier]}, {"Timestamp", [get_timestamp()]}, {"SignatureVersion", ["1"]},
     {"Version", ["2007-08-29"]}].

format_param_list(Params) ->
    format_param_list(Params, []).

format_param_list([{Name, Values} | Rest], Accum) when is_list(Values), length(Values) > 1 ->
    format_param_list(Rest, process_mult_values(Name, Values, 1, Accum));

format_param_list([{Name, Value} | Rest], Accum) when is_list(Value), length(Value) == 1 ->
    [V] = Value,
    format_param_list(Rest, Accum ++ [{Name, V}]);

format_param_list([], Accum) ->
    Accum.

process_mult_values(Name, [H|T], Pos, Accum) ->
    process_mult_values(Name, T, Pos + 1, Accum ++ [{lists:flatten([Name, ".", integer_to_list(Pos)]), H}]);

process_mult_values(_Name, [], _Pos, Accum) ->
    Accum.

generate_checksum(Keys, Params) ->
    crypto:start(),
    F = fun(F, S) ->
		{N1, _V1} = F,
		{N2, _V2} = S,
		string:to_lower(N1) < string:to_lower(N2)
	end,
    NP = normalize_params(lists:sort(F, Params)),
    Keys:sign_request(NP).

get_timestamp() ->
    {{Year, Month, Day}, {Hour, Min, Sec}} = calendar:now_to_universal_time(erlang:now()),
    Fmt = lists:flatten(lists:map(fun build_fmt/1, [{y, Year}, {m, Month}, {d, Day}, {h, Hour}, {mm, Min}, {s, Sec}])),
    lists:flatten(io_lib:format(Fmt, [Year, Month, Day, Hour, Min, Sec])).

normalize_params(Params) ->
    F = fun({Name, Value}) ->
		Name ++ Value
	end,
    lists:flatten(lists:map(F, Params)).

build_fmt({y, _Year}) ->
    "~w-";

build_fmt({m, Month}) ->
    pad_zero(Month) ++ "-";

build_fmt({d, Day}) ->
    pad_zero(Day) ++ "T";

build_fmt({h, Hour}) ->
    pad_zero(Hour) ++ ":";

build_fmt({mm, Min}) ->
    pad_zero(Min) ++ ":";

build_fmt({s, Sec}) ->
    pad_zero(Sec) ++ "Z".

pad_zero(Item) ->
    if
	Item < 10 ->
	    "0~w";
	true ->
	    "~w"
    end.

